home *** CD-ROM | disk | FTP | other *** search
/ InfoMagic Internet Tools 1993 July / Internet Tools.iso / RockRidge / mail / mm / ccmd / cmkey.c < prev    next >
Encoding:
C/C++ Source or Header  |  1990-12-18  |  13.9 KB  |  435 lines

  1. /*
  2.  Copyright (c) 1986, 1990 by The Trustees of Columbia University in
  3.  the City of New York.  Permission is granted to any individual or
  4.  institution to use, copy, or redistribute this software so long as it
  5.  is not sold for profit, provided this copyright notice is retained.
  6.  
  7.  Author: Andrew Lowry
  8. */
  9. /* cmkey
  10. **
  11. ** Code to parse keywords.  Parsing succeeds if current input contains
  12. ** a delimiter, and the input up to that delimiter is nonempty and
  13. ** uniquely matches a non-ignored keyword in the supplied keyword
  14. ** table.  Completion succeeds in the same circumstances, except that
  15. ** no delimiter will be present, and returns the remainder of the
  16. ** matched keyword.  Standard help prints a list of all visible keywords
  17. ** that might match the current input.  The break table specifies which
  18. ** characters are allowed in keywords.  The standard table allows letters,
  19. ** digits, and hyphens in all positions.
  20. **/
  21.  
  22. #define    KEYERR                /* keyword error tbl allocated here */
  23.  
  24. #include "ccmdlib.h"            /* get standard symbols */
  25. #include "cmfncs.h"        /* and internal symbols */
  26.  
  27. /* Forward declaration of handler routines */
  28.  
  29. int keyprs(), keyhlp(), keycplt();
  30.         
  31. static brktab keybrk = {                /* standard break table */
  32.   {                    /* 1st char break array */
  33.                     /* all but letters, digits, hyphen */
  34.     0xff, 0xff, 0xff, 0xff, 0xff, 0xfb, 0x00, 0x3f, 
  35.     0x80, 0x00, 0x00, 0x1f, 0x80, 0x00, 0x00, 0x1f
  36.   },
  37.   {                    /* subsequent char break array */
  38.                     /* same as above */
  39.     0xff, 0xff, 0xff, 0xff, 0xff, 0xfb, 0x00, 0x3f, 
  40.     0x80, 0x00, 0x00, 0x1f, 0x80, 0x00, 0x00, 0x1f
  41.   }
  42. };
  43.  
  44. ftspec ft_key = { keyprs, keyhlp, keycplt, 0, &keybrk }; /* handler structure */
  45.  
  46.  
  47.  
  48. /* keyprs - Succeed if the current input is terminated and matches
  49. ** a single keyword (or exactly matches a non-ignored keyword).
  50. **/
  51.  
  52. PASSEDSTATIC int
  53. keyprs(text,textlen,fdbp,parselen,value)
  54. char *text;
  55. int textlen,*parselen;
  56. fdb *fdbp;
  57. pval *value;
  58. {
  59.   int mcount;                /* number of matches */
  60.   keywrd *mat;                /* the matching keyword */
  61.   char *term;                /* char terminating keyword */
  62.   keytab *kt;                /* the keyword table */
  63.  
  64.   mcount = match(text,textlen,fdbp,&mat,&term);    /* find matching keys */
  65.   if (mcount == 0)
  66.     return(KEYxNM);            /* no match */
  67.   else if (term == NULL)        /* not terminated? */
  68.     return(CMxINC);            /* then incomplete parse */
  69.   else if (mcount > 1)
  70.     return(KEYxAMB);            /* ambiguous */
  71.  
  72.   kt = (keytab *) fdbp->_cmdat;        /* unique - get table address */
  73.   while ((mat)->_kwflg & KEY_ABR)    /* track down abbreviations */
  74.     if (((mat)->_kwval >= kt->_ktcnt) || /* abbrev ptr out of bounds? */
  75.     ((mat)->_kwval < 0)
  76.        )
  77.       return(KEYxABR);            /* bad abbrev chain */
  78.     else
  79.       mat = &(kt->_ktwds[mat->_kwval]);    /* move down chain */
  80.  
  81.   *parselen = term - text;        /* compute field length */
  82.  
  83.   if (fdbp->_cmffl & KEY_EMO) {        /* exact match only? */
  84.       if (*parselen != strlen (mat->_kwkwd))
  85.       return KEYxNM;        /* it's not a match */
  86.   }
  87.   if (fdbp->_cmffl & KEY_PTR)
  88.       value->_pvkey = (keyval) mat;    /* copy value from keyword */
  89.   else
  90.       value->_pvkey = mat->_kwval;
  91.   return(CMxOK);            /* and give a good parse */
  92. }
  93.  
  94.  
  95.  
  96. /* keycplt - If the current input is ambiguous, we beep.  Otherwise
  97. ** we complete with the remainder of the identified keyword.  In the
  98. ** latter case, full completion adds a space and a wakeup, while
  99. ** partial completion stops after punctuation.
  100. **/
  101.  
  102. PASSEDSTATIC int
  103. keycplt(text,textlen,fdbp,full,cplt,cpltlen)
  104. char *text,**cplt;
  105. int textlen,full,*cpltlen;
  106. fdb *fdbp;
  107. {
  108.   int mcount;                /* number of matching keywords */
  109.   keywrd *mat;                /* unique matching key */
  110.   char *term;                /* char terminating the keyword */
  111.   keytab *kt;                /* the keyword table */
  112.  
  113.   *cplt = NULL;                /* assume empty completion */
  114.   mcount = match(text,textlen,fdbp,&mat,&term);    /* find matching keys */
  115.   if (mcount > 1) {
  116.     char *partial();
  117.     *cplt = partial(text,textlen, fdbp->_cmdat,mcount);
  118.     *cpltlen = strlen(*cplt);
  119.     return(CMP_BEL);            /* beep if ambiguous */
  120.   }
  121.   else {
  122.     kt = (keytab *) fdbp->_cmdat;    /* unique - get table address */
  123.     while ((mat)->_kwflg & KEY_ABR)    /* track down abbreviations */
  124.       mat = &(kt->_ktwds[mat->_kwval]);    /* move down chain */
  125.  
  126.     *cplt = mat->_kwkwd+textlen;    /* point at completion text */
  127.     *cpltlen = strlen(mat->_kwkwd) - textlen; /* compute completion len */
  128.     if (*cpltlen < 0)            /* can happen on weird abbrevs */
  129.       *cplt = NULL;            /* no completion in that case */
  130.     if (full)                /* full completion? */
  131.       return(CMP_SPC | CMP_GO);     /* succeed with space and wakeup */
  132.     else
  133.       return(CMP_PNC);            /* partial stops after punctuation */
  134.   }
  135. }
  136.  
  137. /* keyhlp - Construct a set of matching keywords for the current input,
  138. ** then print them in tabular form.  Do not print invisible keywords.
  139. ** If no keywords match, print a special indication.  The help message
  140. ** starts with "keyword, one of the following:".  If custom help has
  141. ** been given, "keyword, " is left off of this string.
  142. **/
  143.  
  144. PASSEDSTATIC int
  145. keyhlp(text,textlen,fdbp,cust,lines)
  146. char *text;
  147. int textlen,cust;
  148. fdb *fdbp;
  149. int lines;
  150. {
  151.   int mcount;                /* number of matches */
  152.   keywrd *mat;                /* a matching keyword */
  153.   char *term;                /* character terminating input */
  154.   keytab *kt;                /* keyword table */
  155.   int i,j;
  156.   int keylen;                /* lengths of individual keywords */
  157.   int cols;                /* number of words printed per line */
  158.   int curcol;                /* current table column */
  159.   int maxlen = 0;            /* maximum keyword length */
  160.   int mylines = 0;
  161.  
  162.   if (!cust)
  163.     cmxputs("keyword, ");        /* start of msg with no custom help */
  164.   cmxputs("one of the following:");    /* remainder of the first line */
  165.       
  166.   mcount = match(text,textlen,fdbp,&mat,&term);    /* find matching keys */
  167.   if (mcount == 0) {            /* no match */
  168.     cmxputs("\n");
  169.     lines--;
  170.     if (lines == 0) {
  171.       if (!cmhelp_more("--space to continue, Q to stop--"))
  172.     return(-1);
  173.       else {
  174.     lines = cmcsb._cmrmx;
  175.     mylines = 0;
  176.       }
  177.     }
  178.     cmxputs(" (No keywords match current input)"); /* indicate void */
  179.     return(lines-1);
  180.   }
  181.   mylines = 1;
  182.   kt = (keytab *) fdbp->_cmdat;        /* get keywrd table from FDB */
  183.   mat = kt->_ktwds;            /* point to first keyword */
  184.   if ((fdbp->_cmffl & KEY_WID) &&    /* fixed width requested */
  185.       (kt->_ktwid > 0)) {        /* sanity check */
  186.     maxlen = kt->_ktwid;
  187.   }
  188.   else {
  189.     for (i = 0; i < kt->_ktcnt; i++) {    /* first pass to find longest kwd */
  190.       if ((mat->_kwflg & KEY_MAT) &&     /* matching keyword... */
  191.       ((mat->_kwflg & KEY_INV) == 0) /* and not invisible */
  192.       ) {
  193.     keylen = strlen(mat->_kwkwd);    /* get keyword length */
  194.     if (keylen > maxlen)
  195.       maxlen = keylen;        /* update longest size */
  196.       }
  197.       mat++;                /* and move to next keyword */
  198.     }
  199.     maxlen += 3;            /* adjust for column separation */
  200.   }
  201.  
  202.   /* Following calculation goes as follows:
  203.   ** Let w be the screen width.  Naively, we would calculate that
  204.   ** we could list floor(w/maxlen) keywords per line, each taking up
  205.   ** maxlen columns on the screen.  But since each line is indented
  206.   ** two spaces, we should subtract 2 from the effective screen width.
  207.   ** Then we add 3 to the effective width because we will not need to
  208.   ** print three spaces after the last keyword in a line.  So the
  209.   ** effective screen width is w-2+3 = w+1 = cmcsb._cmcmx+2, since
  210.   ** cmcsb._cmcmx is one less than the screen width.  So we want
  211.   ** floor((cmcsb._cmcmx+2)/maxlen) keywords per line.
  212.   **/
  213.  
  214.   /*
  215.    * The reasoning above ignores the problem with automargins on non-"xn"
  216.    * terminals.  On an 80-column terminal with three columns of 24-char
  217.    * keywords, for instance, the terminal will wrap around to the next
  218.    * line and the cmxnl() below would be inappropriate.  So until someone
  219.    * cleans up the rest of the ccmd output routines, we have to avoid the
  220.    * rightmost column.
  221.    *
  222.    * MM happens to have 24-character keywords which would look kind of silly
  223.    * displayed in only two columns, so we compensate by subtracting one of
  224.    * the spaces displayed at the beginning of the line.  TOPS-20 only prints
  225.    * one space at the beginning of the line, so I don't feel too guilty
  226.    * about this... -- chris
  227.    */
  228.  
  229.   cols = (cmcsb._cmcmx+2) / maxlen;    /* number of columns per line */
  230.   if (cols < 1) cols = 1;        /* don't divide by zero */
  231.  
  232.   mat = kt->_ktwds;            /* point to first keyword */
  233.   curcol = 0;                /* currently printing first column */
  234.   for (i = 0; i < kt->_ktcnt; i++) {    /* second pass to print matches */
  235.     if ((mat->_kwflg & KEY_MAT) &&    /* matching keyword? */
  236.     ((mat->_kwflg & KEY_INV) == 0)    /* and visible? */
  237.        ) {
  238.       if (curcol == 0) {
  239.     cmxnl();            /* new line for first column */
  240.     if (mylines >= lines) {
  241.       if (!cmhelp_more("--space to continue, Q to stop--"))
  242.           return(-1);
  243.       else {
  244.           lines = cmcsb._cmrmx;
  245.           mylines = 0;
  246.       }
  247.     }
  248.     mylines++;            /* and count the line */
  249.     cmxputs(" ");            /* and offset a bit */
  250.       }
  251.       cmxputs(mat->_kwkwd);        /* print the keyword */
  252.       if (curcol < (cols-1))        /* space out if not last column */
  253.     for (j = strlen(mat->_kwkwd); j < maxlen; j++)
  254.        cmxputc(SPACE);
  255.       curcol = (curcol+1) % cols;    /* and move to next column */
  256.     }
  257.     mat++;                /* move to next keyword */
  258.   }
  259.   return(lines - mylines);            /* all done */
  260. }
  261.  
  262.  
  263.  
  264. /* match - Auxiliary routine used by keyword handlers.
  265. **
  266. ** Purpose:
  267. **   Step through all the keywords in a table, and set their KEY_MAT
  268. **   flags according to whether or not they match a given input string.
  269. **   Returns the number of matching keywords, and if only one keyword
  270. **   matches, a pointer to that keyword.  If more than one keyword
  271. **   matches, but one keyword matches exactly, it is returned as if
  272. **   it were the only matching keyword.  KEY_INV, KEY_NOR and KEY_ABR
  273. **   flags do not affect this operation, except that an exact match
  274. **   to a KEY_NOR flagged keyword will not be considered exact.
  275. **
  276. ** Input arguments:
  277. **   text - A pointer to the first input character.
  278. **   textlen - The number of input characters.
  279. **   fdbp - A pointer to the FDB pointing to the keyword table
  280. **     and break table to be used.
  281. ** 
  282. ** Output arguments:
  283. **   mat - A pointer to the matching keyword, if exactly one keyword
  284. **     matched or if one of the matching keywords was an exact match.
  285. **   term - A pointer to the first character following the input that
  286. **     was used to match keywords, if any characters were left after
  287. **     that input.  Otherwise, NULL is returned here.
  288. **
  289. ** Returns: The number of matching keywords (or 1 for an exact match).
  290. **/
  291.  
  292. static int
  293. match(text,textlen,fdbp,mat,term)
  294. char *text,**term;
  295. int textlen;
  296. fdb *fdbp;
  297. keywrd **mat;
  298. {
  299.   int mcount = 0;            /* number of matches seen */
  300.   int inlen;                /* # of chars to match in input */
  301.   int i;
  302.   int exact = FALSE;            /* true if exact match occurs */
  303.   keywrd *kwds;                /* for stepping through table */
  304.   brktab *btab;                /* break table to use */
  305.   keytab *kt;                /* keyword table to search */
  306.   
  307.   if ((btab = fdbp->_cmbrk) == NULL)    /* get supplied break table */
  308.     btab = &keybrk;            /* or use default */
  309.  
  310.   for (inlen = 0; inlen < textlen; inlen++) /* find # of usable chars */
  311.     if (BREAK(btab,text[inlen],inlen))    /* stop on first break char */
  312.       break;
  313.   if (inlen == textlen)            /* no break char? */
  314.     *term = NULL;            /* then set no terminator */
  315.   else
  316.     *term = text+inlen;            /* else point to it for caller */
  317.   
  318.   kt = (keytab *) fdbp->_cmdat;        /* point to keyword table */
  319.   if (inlen == 0 && textlen != 0)
  320.       return(0);
  321.   kwds = kt->_ktwds;            /* point to first keyword */
  322.   for (i = 0; i < kt->_ktcnt; i++) {    /* step through table */
  323.     if (match1(kwds->_kwkwd,text,inlen)) {
  324.       kwds->_kwflg |= KEY_MAT;        /* this keyword matches */
  325.       if (!exact) {            /* if no prior exact match */
  326.     *mat = kwds;            /* then save pointer to return */
  327.         mcount++;            /* and count it */
  328.       }
  329.       if (strlen(kwds->_kwkwd) == inlen) /* exact match? */
  330.         if ((kwds->_kwflg & KEY_NOR) == 0) { /* and not ignored? */
  331.       exact = TRUE;            /* flag this exact match */
  332.           mcount = 1;            /* only one match now */
  333.         }
  334.     }
  335.     else
  336.       kwds->_kwflg &= ~KEY_MAT;        /* no match -- turn off flag */
  337.  
  338.     kwds++;                /* move on to next keyword */
  339.   }
  340.   kwds = kt->_ktwds;
  341.   if (mcount == 1)
  342.     for (i = 0; i < kt->_ktcnt; i++,kwds++) {    /* step through table */
  343.       if (kwds->_kwflg & KEY_MAT && kwds->_kwflg & KEY_NOR) {
  344.     kwds->_kwflg &= ~KEY_MAT;
  345.     mcount = 0;
  346.     *mat = NULL;
  347.     break;
  348.       }
  349.     }
  350.   return(mcount);            /* give back # of matches */
  351. }
  352.  
  353.  
  354.  
  355. /* match1 - Auxiliary routine for match 
  356. **
  357. ** Purpose:
  358. **   Decides whether or not two strings match up to a given number of
  359. **   characters.  Case of letters is ignored in the comparison.
  360. **
  361. ** Input arguments:
  362. **   s1, s2 - Pointers to the strings to be compared.
  363. **   slen - Number of characters to compare.
  364. **
  365. ** Output arguments: None.
  366. ** Returns: TRUE for a match, FALSE otherwise.
  367. **/
  368.  
  369. static int
  370. match1(s1,s2,slen)
  371. char *s1,*s2;
  372. int slen;
  373. {
  374.   char c1,c2;            /* individual chars to compare */
  375.   while (slen-- > 0) {        /* step through strings */
  376.     c1 = (*s1++) & CC_CHR;        /* pick up next pair of chars */
  377.     c2 = (*s2++) & CC_CHR;
  378.     if (islower(c1))
  379.       c1 = toupper(c1);        /* upper case first char */
  380.     if (islower(c2))
  381.       c2 = toupper(c2);        /* upper case second char */
  382.     if (c1 != c2)
  383.       return(FALSE);        /* mismatch */
  384.   }
  385.   return(TRUE);            /* all chars matched */
  386. }
  387.  
  388. PASSEDSTATIC char *
  389. partial(text,textlen,kt,pcount) 
  390. char *text; 
  391. int textlen;
  392. keytab *kt;
  393. int pcount;
  394. {
  395.     int i;
  396.     static char buf[50];
  397.     keywrd *k;
  398.     int first = TRUE;
  399.     int len;
  400.  
  401.     buf[0] = '\0';
  402.     for(i = 0; i < kt->_ktcnt; i++) {
  403.     k = &kt->_ktwds[i];
  404.     if (!(k->_kwflg & KEY_MAT))
  405.         continue;
  406.     if (k->_kwflg & KEY_NOR)
  407.         continue;
  408.     if (first) {
  409.         strcpy(buf,k->_kwkwd);
  410.         first = FALSE;
  411.     }
  412.     else  {
  413.         len = matchlen(buf,k->_kwkwd);
  414.         buf[len] = '\0';
  415.         if (len == textlen)
  416.         break;
  417.     }
  418.     }
  419.     return(&buf[textlen]);
  420. }
  421.  
  422. #ifdef toupper
  423. #undef toupper
  424. #endif
  425. #define toupper(c) (islower(c) ? (c)-'a'+'A' : (c))
  426. matchlen(s1,s2)
  427. register char *s1, *s2;
  428. {
  429.     register int i;
  430.  
  431.     for(i = 0; (toupper(*s1) == toupper(*s2)); s1++,s2++,i++);
  432.     return(i);
  433. }
  434.     
  435.